#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>

#define ALRMMASK (1<<(SIGALRM-1))

#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/system.h>

#define _L_FLAG(tty,f)  ((tty)->termios.c_lflag & f)
#define _I_FLAG(tty,f)  ((tty)->termios.c_iflag & f)
#define _O_FLAG(tty,f)  ((tty)->termios.c_oflag & f)

#define L_CANON(tty)    _L_FLAG((tty),ICANON)
#define L_ISIG(tty)    _L_FLAG((tty),ISIG)
#define L_ECHO(tty)     _L_FLAG((tty),ECHO)
#define L_ECHOE(tty)     _L_FLAG((tty),ECHOE)
#define L_ECHOCTL(tty)  _L_FLAG((tty),ECHOCTL)

#define O_POST(tty)     _O_FLAG((tty),OPOST)
#define O_NLCR(tty)     _O_FLAG((tty),ONLCR)
#define O_CRNL(tty)     _O_FLAG((tty),OCRNL)
#define O_NLRET(tty)    _O_FLAG((tty),ONLRET)
#define O_LCUC(tty)     _O_FLAG((tty),OLCUC)

#define I_UCLC(tty)     _I_FLAG((tty),IUCLC)
#define I_NLCR(tty)     _I_FLAG((tty),INLCR)
#define I_CRNL(tty)     _I_FLAG((tty),ICRNL)
#define I_NOCR(tty)     _I_FLAG((tty),IGNCR)


struct tty_struct tty_table[] = {
    {
        {
            ICRNL,
            OPOST | ONLCR,
            0,
            ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,
            0,
            INIT_C_CC
        },
        0,
        0,
        0,
        con_write,
        {0, 0, 0, 0, ""},
        {0, 0, 0, 0, ""},
        {0, 0, 0, 0, ""},
    }
};


struct tty_queue * table_list[]={
    &tty_table[0].read_q, &tty_table[0].write_q,
};


void tty_init() {
    con_init();
}

static void sleep_if_empty(struct tty_queue * queue) {
     cli();
     while (!(current->signal & ~current->blocked) && EMPTY(*queue))
         interruptible_sleep_on(&queue->proc_list);
     sti();
}

static void sleep_if_full(struct tty_queue * queue) {
    if (!FULL(*queue))
        return;

    cli();
    while (!(current->signal & ~current->blocked) && LEFT(*queue)<128)
        interruptible_sleep_on(&queue->proc_list);
    sti();
}

int tty_signal(int sig, struct tty_struct *tty) {
    if (is_orphaned_pgrp(current->pgrp))
        return -EIO;

    (void) kill_pg(current->pgrp,sig,1);
    if ((current->blocked & (1<<(sig-1))) ||
            ((int) current->sigaction[sig-1].sa_handler == 1))
        return -EIO;

    else if (current->sigaction[sig-1].sa_handler)
        return -EINTR;

    else
        return -ERESTARTSYS;
}

int tty_read(unsigned channel, char * buf, int nr) {
    struct tty_struct * tty;
    char c, * b=buf;
    int minimum,time;

    if (channel > 2 || nr < 0)
        return -1;

    tty = tty_table + channel;

    if ((current->tty == channel) && (tty->pgrp != current->pgrp))
        return(tty_signal(SIGTTIN, tty));

    time = 10L*tty->termios.c_cc[VTIME];
    minimum = tty->termios.c_cc[VMIN];

    if (L_CANON(tty)) {
        minimum = nr;
        current->timeout = 0xffffffff;
        time = 0;
    }
    else if (minimum) {
        current->timeout = 0xffffffff;
    }
    else {
        minimum = nr;
        if (time)
            current->timeout = time + jiffies;
        time = 0;
    }
    if (minimum>nr)
        minimum = nr;

    while (nr > 0) {
        cli();
        if (EMPTY(tty->secondary) || (L_CANON(tty) &&
            !FULL(tty->read_q) && !tty->secondary.data)) {
            if (!current->timeout ||
                    (current->signal & ~current->blocked)) {
                sti();
                break;
            }
            interruptible_sleep_on(&tty->secondary.proc_list);
            sti();
            continue;
        }
        sti();

        do {
            GETCH(tty->secondary, c);
            if ((c == EOF_CHAR(tty)) || (c == 10)) {
                tty->secondary.data--;
            }
            if ((c == EOF_CHAR(tty)) && L_CANON(tty)) {
                return (b - buf);
            }
            else {
                put_fs_byte(c,b++);
                if (!--nr)
                    break;
            }
        } while (nr>0 && !EMPTY(tty->secondary));

        wake_up(&tty->read_q.proc_list);
        if (time)
            current->timeout = time+jiffies;
        if (L_CANON(tty) || b-buf >= minimum)
            break;
    }

    current->timeout = 0;
    if ((current->signal & ~current->blocked) && !(b-buf))
        return -ERESTARTSYS;

    return (b - buf);
}

int tty_write(unsigned channel, char* buf, int nr) {
    static int cr_flag = 0;
    struct tty_struct * tty;
    char c, *b=buf;

    if (channel > 2 || nr < 0)
        return -1;

    tty = tty_table + channel;

    while (nr > 0) {
        sleep_if_full(&tty->write_q);
        if (current->signal & ~current->blocked)
            break;

        while (nr>0 && !FULL(tty->write_q)) {
            c = get_fs_byte(b);
            if (O_POST(tty)) {
                if (c=='\r' && O_CRNL(tty))
                    c = '\n';
                else if (c=='\n' && O_NLRET(tty))
                    c = '\r';

                if (c=='\n' && !cr_flag && O_NLCR(tty)) {
                    cr_flag = 1;
                    PUTCH(13,tty->write_q);
                    continue;
                }

                if (O_LCUC(tty)) {
                    c = toupper(c);
                }
            }

            b++; nr--;
            cr_flag = 0;
            PUTCH(c,tty->write_q);
        }
        tty->write(tty);
        if (nr>0)
            schedule();
    }
    return (b-buf);
}

void copy_to_cooked(struct tty_struct * tty) {
    signed char c; 

    while (1) {
        if (EMPTY(tty->read_q))
            break;
        if (FULL(tty->secondary))
            break;
        GETCH(tty -> read_q, c);

        if (c == 13) {
            if (I_CRNL(tty))
                c = 10;
            else if (I_NOCR(tty))
                continue;
        }
        else if (c == 10 && I_NLCR(tty))
            c = 13;

        if (I_UCLC(tty))
            c = tolower(c);

        if (L_CANON(tty)) {
            if (c == KILL_CHAR(tty)) {
                while (!(EMPTY(tty->secondary) ||
                            (c = LAST(tty->secondary)) == 10 ||
                            c == EOF_CHAR(tty))) {
                    if (L_ECHO(tty)) {
                        if (c < 32)
                            PUTCH(127, tty->write_q);
                        PUTCH(127, tty->write_q);
                        tty->write(tty);
                    }
                    DEC(tty->secondary.head);
                }
                continue;
            }
           
            if (c == ERASE_CHAR(tty)) {
                if (EMPTY(tty->secondary) ||
                        (c = LAST(tty->secondary)) == 10 ||
                        c == EOF_CHAR(tty))
                    continue; 
                if (L_ECHO(tty)) {
                    if (c < 32)
                        PUTCH(127, tty->write_q);
                    PUTCH(127, tty->write_q);
                    tty->write(tty);
                }
                DEC(tty->secondary.head);
                continue;
            }
           
            if (c == STOP_CHAR(tty)) {
                tty->stopped = 1;
                continue;
            }
           
            if (c == START_CHAR(tty)) {
                tty->stopped = 0;
                continue;
            }
        }
       
        if (c == 10 || c == EOF_CHAR(tty))
            tty->secondary.data++;
       
        if (L_ECHO(tty)) {
            if (c == 10) {
                PUTCH(10, tty->write_q);
                PUTCH(13, tty->write_q);
            }
            else if (c < 32) {
                if (L_ECHOCTL(tty)) {
                    PUTCH('^', tty->write_q);
                    PUTCH(c + 64, tty->write_q);
                }
            }
            else
                PUTCH(c, tty->write_q);
            tty->write(tty);
        }
        PUTCH(c, tty->secondary);
    }
    wake_up(&tty->secondary.proc_list);
}

void do_tty_interrupt(int tty) {
    copy_to_cooked(tty_table + tty);
}

